package com.onlyxiahui.extend.pdf.table;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.List;

import org.apache.commons.cli.ParseException;
import org.apache.pdfbox.pdmodel.PDDocument;

import com.onlyxiahui.extend.pdf.table.data.TextPage;
import com.onlyxiahui.extend.pdf.table.data.TextRow;
import com.onlyxiahui.extend.pdf.table.data.TextTable;

import technology.tabula.ObjectExtractor;
import technology.tabula.Page;
import technology.tabula.PageIterator;
import technology.tabula.Rectangle;
import technology.tabula.RectangularTextContainer;
import technology.tabula.Table;
import technology.tabula.detectors.DetectionAlgorithm;
import technology.tabula.detectors.NurminenDetectionAlgorithm;
import technology.tabula.extractors.BasicExtractionAlgorithm;
import technology.tabula.extractors.SpreadsheetExtractionAlgorithm;
import technology.tabula.writers.CSVWriter;
import technology.tabula.writers.JSONWriter;
import technology.tabula.writers.Writer;

/**
 * Description <br>
 * Date 2020-02-02 20:45:21<br>
 * 
 * @author XiaHui [onlovexiahui@qq.com]<br>
 * @since 1.0.0
 */

public class AnalysisService {

	public void analysisCsv(File pdfFile, String password, File csv) {
		List<Table> tables = analysisToTables(pdfFile, password);
		Writer writer = new CSVWriter();
		try {
			extractFileInto(tables, writer, csv);
		} catch (ParseException e) {
			e.printStackTrace();
		}
	}

	public String analysisJson(File pdfFile, String password) {
		String json = null;
		List<Table> tables = analysisToTables(pdfFile, password);
		Writer writer = new JSONWriter();
		json = extractFileInto(tables, writer);
		return json;
	}

	public List<Table> analysisToTables(File pdfFile, String password) {
		List<Table> tables = new ArrayList<>();
		List<List<Table>> pageTables = analysisToPageTables(pdfFile, password);
		for (List<Table> ts : pageTables) {
			tables.addAll(ts);
		}
		return tables;
	}

	public List<String> analysisCsvList(File pdfFile, String password) {
		List<String> list = new ArrayList<>();
		List<List<Table>> pageTables = analysisToPageTables(pdfFile, password);
		Writer writer = new CSVWriter();
		for (List<Table> ts : pageTables) {
			list.add(extractFileInto(ts, writer));
		}
		return list;
	}

	public List<TextPage> analysisTextPageList(File pdfFile, String password) {
		return analysisTextPageList(ExtractionMethod.DECIDE, pdfFile, password);
	}

	@SuppressWarnings("rawtypes")
	public List<TextPage> analysisTextPageList(ExtractionMethod method, File pdfFile, String password) {
		List<TextPage> list = new ArrayList<>();
		List<List<Table>> pageTables = analysisToPageTables(method, pdfFile, password);
		for (List<Table> ts : pageTables) {
			TextPage p = new TextPage();
			for (Table table : ts) {
				TextTable t = new TextTable();
				for (List<RectangularTextContainer> row : table.getRows()) {
					TextRow r = new TextRow();
					for (RectangularTextContainer<?> tc : row) {
						r.addCell(tc.getText());
					}
					t.addRow(r);
				}
				p.addTable(t);
			}
			list.add(p);
		}
		return list;
	}

	public List<List<Table>> analysisToPageTables(File pdfFile, String password) {
		return analysisToPageTables(ExtractionMethod.DECIDE, pdfFile, password);
	}

	public List<List<Table>> analysisToPageTables(ExtractionMethod method, File pdfFile, String password) {

		List<List<Table>> list = new ArrayList<>();

		List<Integer> pages = null;
		// new ArrayList<>();
		// pages.add(2);
		PDDocument pdfDocument = null;
		try {
			pdfDocument = password == null ? PDDocument.load(pdfFile) : PDDocument.load(pdfFile, password);
			PageIterator pageIterator = getPageIterator(pdfDocument, pages);

			TableExtractor tableExtractor = new TableExtractor();
			tableExtractor.setMethod(method);
			tableExtractor.setGuess(true);
			tableExtractor.setUseLineReturns(true);

			while (pageIterator.hasNext()) {
				Page page = pageIterator.next();
				List<Table> tables = new ArrayList<>();
				tables.addAll(tableExtractor.extractTables(page));
				list.add(tables);
			}
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			try {
				if (pdfDocument != null) {
					pdfDocument.close();
				}
			} catch (IOException e) {
				System.out.println("Error in closing pdf document" + e);
			}
		}
		return list;
	}

	public String extractFileInto(List<Table> tables, Writer writer) {
		StringWriter stringWriter = null;
		try {
			stringWriter = new StringWriter();
			writeTables(tables, writer, stringWriter);
			return stringWriter.toString();
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			if (stringWriter != null) {
				try {
					stringWriter.close();
				} catch (IOException e) {
					System.out.println("Error in closing the BufferedWriter" + e);
				}
			}
		}
		return null;
	}

	public void extractFileInto(List<Table> tables, Writer writer, File outputFile) throws ParseException {
		BufferedWriter bufferedWriter = null;
		try {
			FileWriter fileWriter = new FileWriter(outputFile.getAbsoluteFile());
			bufferedWriter = new BufferedWriter(fileWriter);
			outputFile.createNewFile();
			writeTables(tables, writer, fileWriter);
		} catch (IOException e) {
			throw new ParseException("Cannot create file " + outputFile);
		} finally {
			if (bufferedWriter != null) {
				try {
					bufferedWriter.close();
				} catch (IOException e) {
					System.out.println("Error in closing the BufferedWriter" + e);
				}
			}
		}
	}

	private void writeTables(List<Table> tables, Writer writer, Appendable out) throws IOException {
		// new JSONWriter();
		// writer = new CSVWriter();
		writer.write(out, tables);
	}

	private PageIterator getPageIterator(PDDocument pdfDocument, List<Integer> pages) throws IOException {
		ObjectExtractor extractor = new ObjectExtractor(pdfDocument);
		return (pages == null) ? extractor.extract() : extractor.extract(pages);
	}

	private static class TableExtractor {
		private boolean guess = false;
		private boolean useLineReturns = false;
		private BasicExtractionAlgorithm basicExtractor = new BasicExtractionAlgorithm();
		private SpreadsheetExtractionAlgorithm spreadsheetExtractor = new SpreadsheetExtractionAlgorithm();
		private List<Float> verticalRulingPositions = null;
		private ExtractionMethod method = ExtractionMethod.BASIC;

		public TableExtractor() {
		}

		@SuppressWarnings("unused")
		public void setVerticalRulingPositions(List<Float> positions) {
			this.verticalRulingPositions = positions;
		}

		@SuppressWarnings("unused")
		public void setGuess(boolean guess) {
			this.guess = guess;
		}

		@SuppressWarnings("unused")
		public void setUseLineReturns(boolean useLineReturns) {
			this.useLineReturns = useLineReturns;
		}

		@SuppressWarnings("unused")
		public void setMethod(ExtractionMethod method) {
			this.method = method;
		}

		public List<Table> extractTables(Page page) {
			ExtractionMethod effectiveMethod = this.method;
			if (effectiveMethod == ExtractionMethod.DECIDE) {
				effectiveMethod = spreadsheetExtractor.isTabular(page) ? ExtractionMethod.SPREADSHEET : ExtractionMethod.BASIC;
			}
			switch (effectiveMethod) {
			case BASIC:
				return extractTablesBasic(page);
			case SPREADSHEET:
				return extractTablesSpreadsheet(page);
			default:
				return new ArrayList<>();
			}
		}

		public List<Table> extractTablesBasic(Page page) {
			if (guess) {
				// guess the page areas to extract using a detection algorithm
				// currently we only have a detector that uses spreadsheets to find table areas
				DetectionAlgorithm detector = new NurminenDetectionAlgorithm();
				List<Rectangle> guesses = detector.detect(page);
				List<Table> tables = new ArrayList<>();

				for (Rectangle guessRect : guesses) {
					Page guess = page.getArea(guessRect);
					tables.addAll(basicExtractor.extract(guess));
				}
				return tables;
			}

			if (verticalRulingPositions != null) {
				return basicExtractor.extract(page, verticalRulingPositions);
			}
			return basicExtractor.extract(page);
		}

		public List<Table> extractTablesSpreadsheet(Page page) {
			// TODO add useLineReturns
			return spreadsheetExtractor.extract(page);
		}
	}
}
